package com.jdd.modules.sdkidentifyresultpushlog.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jdd.common.api.vo.RedisVo;
import com.jdd.common.api.vo.Result;
import com.jdd.common.api.vo.ResultVo;
import com.jdd.common.api.vo.WebSocketVo;
import com.jdd.common.constant.CacheConstant;
import com.jdd.common.constant.JddConstant;
import com.jdd.common.exception.JddBootException;
import com.jdd.common.util.*;
import com.jdd.config.websocket.WebSocketServer;
import com.jdd.modules.configcartype.entity.ConfigCarType;
import com.jdd.modules.configcartype.service.IConfigCarTypeService;
import com.jdd.modules.operationLog.entity.ParkHandOverLog;
import com.jdd.modules.operationLog.mapper.ParkHandOverLogMapper;
import com.jdd.modules.operationLog.service.IParkHandOverLogService;
import com.jdd.modules.order.entity.ParkOrderInfo;
import com.jdd.modules.order.mapper.ParkOrderInfoMapper;
import com.jdd.modules.parkcar.entity.*;
import com.jdd.modules.parkcar.mapper.ParkCarMonthlyMapper;
import com.jdd.modules.parkcar.mapper.ParkCarStoredMapper;
import com.jdd.modules.parkcar.mapper.ParkInnerListMapper;
import com.jdd.modules.parkcar.mapper.ParkWhiteListMapper;
import com.jdd.modules.parkcar.service.IParkCarOutAbnormalLogService;
import com.jdd.modules.parkcar.service.IParkSpaceService;
import com.jdd.modules.parking.entity.*;
import com.jdd.modules.parking.mapper.ParkArmInfoMapper;
import com.jdd.modules.parking.mapper.ParkCarEnterLogMapper;
import com.jdd.modules.parking.mapper.ParkCarOutLogMapper;
import com.jdd.modules.parking.service.*;
import com.jdd.modules.sdk.LedCallInterface;
import com.jdd.modules.sdk.SdkCallInterface;
import com.jdd.modules.sdkidentifyresultpushlog.entity.*;
import com.jdd.modules.sdkidentifyresultpushlog.mapper.SdkIdentifyResultPushLogMapper;
import com.jdd.modules.sdkidentifyresultpushlog.service.ChargeFeeService;
import com.jdd.modules.sdkidentifyresultpushlog.service.ISdkIdentifyResultPushLogService;
import com.jdd.modules.sendcommandtoarmlog.entity.SendCommandToArmLog;
import com.jdd.modules.sendcommandtoarmlog.service.ISendCommandToArmLogService;
import com.jdd.modules.system.util.MoblieControllerUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

import javax.annotation.Resource;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

import static com.jdd.common.constant.CacheConstant.CURRENT_EXIT_LICENSE_PLATE_TEN;

/**
 * @author ：jdd孙庆伟
 * @date ：Created in 2021/9/3 10:50
 * @description：
 * @modified By：`
 * @version: 1.0
 */
@Service
@Slf4j
public class ISdkIdentifyResultPushLogServiceImpl extends ServiceImpl<SdkIdentifyResultPushLogMapper, SdkIdentifyResultPushLog> implements ISdkIdentifyResultPushLogService {
    @Resource
    private IConfigParkInfoService configParkInfoService;
    @Resource
    private ParkCarEnterLogMapper parkCarEnterLogMapper;
    @Resource
    private ParkCarOutLogMapper parkCarOutLogMapper;
    @Resource
    private ParkWhiteListMapper whiteListMapper;
    @Resource
    private IParkArmInfoService parkArmInfoService;
    @Resource
    private IConfigParkThroughService parkThroughService;
    @Resource
    private IConfigCarTypeService carTypeService;
    @Resource
    private ParkOrderInfoMapper orderInfoMapper;


    @Resource
    private ParkOrderInfoMapper parkOrderInfoMapper;
    @Resource
    private WebSocketServer socketServer;
    @Resource
    private ParkCarMonthlyMapper parkCarMonthlyMapper;
    @Resource
    private ParkCarStoredMapper parkCarStoredMapper;
    @Resource
    private ParkArmInfoMapper parkArmInfoMapper;
    @Resource
    private IparkCarEnterLogService parkCarEnterLogService;
    @Resource
    private IparkCarOutLogService parkCarOutLogService;
    @Resource
    private IParkSpaceService parkSpaceService;
    @Resource
    private RestTemplateUtils restTemplateUtils;
    @Resource
    private CacheUtil cacheUtil;
    @Resource
    private LedCallInterface ledCallInterface;
    @Resource
    private SdkCallInterface sdkCallInterface;
    @Resource
    private ChargeFeeService chargeFeeService;
    @Resource
    private IParkCarOutAbnormalLogService parkCarOutAbnormalLogService;
    @Resource
    private IParkWorkStationService parkWorkStationService;
    @Resource
    private IParkHandOverLogService parkHandOverLogService;
    @Resource
    private ParkHandOverLogMapper parkHandOverLogMapper;

    public String appScrect;

    public String appId;
    @Resource
    private ParkInnerListMapper parkInnerListMapper;
    @Resource
    private ISendCommandToArmLogService commandToArmLogService;

    @Override
    public Result openDaoZhaUploadMessage(ParkArmInfo parkArmInfo) {
        Result result = sdkCallInterface.Sdk485DaoZhaUploadMessage(parkArmInfo.getSerialNo());
        SendCommandToArmLog sendCommandToArmLog = new SendCommandToArmLog();
        sendCommandToArmLog.setId(SnowFlakeUtil.getId());
        sendCommandToArmLog.setParkCode(parkArmInfo.getParkCode());
        sendCommandToArmLog.setParkName(parkArmInfo.getParkName());
        sendCommandToArmLog.setArmId(parkArmInfo.getId());
        sendCommandToArmLog.setIsSuccess(JddConstant.StringNumber.STRING_ONE);
        commandToArmLogService.save(sendCommandToArmLog);
        return result;
    }


    /**
     * 新版计费规则 判断出入场
     *
     * @param identifyResultPushLog 车辆识别信息
     * @param parkArmInfo           出入口配置信息
     * @return void
     * @author jdd孙庆伟
     * @date 2021/9/3 11:32:17
     * @version 1.0
     */
    @Override
    public void identifyJudge(SdkIdentifyResultPushLog identifyResultPushLog, ParkArmInfo parkArmInfo) throws Exception {
        log.info("车辆识别，车场名称：{}，车牌号：{}", parkArmInfo.getParkName(), identifyResultPushLog.getLicense());
        // 新版计费规则的出入场逻辑
        if (StringUtils.isNotBlank(parkArmInfo.getTargetAreaId())) {
            log.info("车道目标区域存在，车辆入场，目标区域id：{}", parkArmInfo.getTargetAreaId());
            // 如果目标区域Id不为空，则是入场
            carEntrance(parkArmInfo, identifyResultPushLog);
            return;
        }
        // 如果目标区id是空，且源区域id不是空，则是出场
        if (StringUtils.isNotBlank(parkArmInfo.getSourceAreaId())) {
            //缓存当前出口车牌
            cacheUtil.put(CacheConstant.CURRENT_EXIT_LICENSE_PLATE, identifyResultPushLog.getSerialNo(), identifyResultPushLog.getLicense() + "#" + identifyResultPushLog.getOssImg() + "#" + identifyResultPushLog.getImg() + "#" + parkArmInfo.getArmName() + "#" + parkArmInfo.getSerialNo());
            log.info("车道源区域存在，目标区域为空，车辆出场，源区域id：{}", parkArmInfo.getSourceAreaId());
            outHandle(parkArmInfo, identifyResultPushLog);
            return;
        }
        throw new JddBootException("车道配置错误");
    }

    /**
     * 车牌识别软触发
     *
     * @param serialNo
     * @return com.jdd.common.api.vo.Result
     * @author jdd孙庆伟
     * @date 2021/9/6 15:15:25
     * @version 1.0
     */
    @Override
    public Result triggerExt(String serialNo) {
        return sdkCallInterface.TriggerExtAll(serialNo);
    }

    /**
     * 确认开闸 拒绝 语音播报
     *
     * @param reChargeVo
     * @return com.jdd.common.api.vo.Result
     * @author jdd孙庆伟
     * @date 2021/9/1 09:33:36
     * @version 1.0
     */
    @Override
    public void refuseCarEnter(ReChargeVo reChargeVo) {
        ledCallInterface.RefuseCarEnter(reChargeVo.getSerialNo(), reChargeVo.getPlateNo());
    }

    /**
     * 确认开闸 允许 语音播报
     *
     * @param reChargeVo
     * @return void
     * @author jdd孙庆伟
     * @date 2021/9/1 11:03:402
     * @version 1.0
     */
    @Override
    public void openedSaveParkCarEnterLog(ReChargeVo reChargeVo, ParkCarEnterLog parkCarEnterLog) {
        //led播报
        ledCallInterface.TemporaryEntranceAll(reChargeVo.getSerialNo(), reChargeVo.getPlateNo());
        //调用云端保存入场记录接口 如果失败重试三次 并返回云端保存结果
        String url = "";
        //上传云端 图片路径修改成oss图片路径
        parkCarEnterLog.setImgPath(parkCarEnterLog.getOssImgPath());
        //云端保存结果 成功200 失败 400
        String remoteResults = getRemoteResults(url, parkCarEnterLog);
        //如果云端保存入场记录成功则修改场端出场记录 是否同步成功状态
        if (remoteResults.equals(JddConstant.SaveCloudType.SAVE_SUCCESS)) {
            parkCarEnterLogMapper.editSynchronousStatus(parkCarEnterLog.getId(), remoteResults);
        }
    }

    /**
     * 功能描述: 补录用，通过序列号和车牌号查到识别记录，把识别记录的数据返回
     *
     * @param serialNo
     * @param plateNo
     * @param parkCode
     * @return: 识别记录
     * @Author: lcy
     * @Date: 2021/9/16
     */
    private SdkIdentifyResultPushLog findBySerialNoAndPlate(String serialNo, String parkCode, String plateNo) {
        QueryWrapper<SdkIdentifyResultPushLog> wrapper = new QueryWrapper<>();
        if (StringUtils.isNotBlank(serialNo)) {
            wrapper.eq("serial_no", serialNo);
        }
        if (StringUtils.isNotBlank(parkCode)) {
            wrapper.eq("park_code", parkCode);
        }
        wrapper.eq("license", plateNo);
        wrapper.orderByDesc("create_time");
        wrapper.last("limit 1");
        List<SdkIdentifyResultPushLog> list = this.list(wrapper);
        return CollectionUtil.isEmpty(list) ? null : list.get(0);
    }

    /**
     * 补录和匹配的重新计费，返回的订单信息的序列号和图片是出口的
     *
     * @param reChargeVo 前端传的数据，序列号和图片是出口
     * @param reChargeVo 前端传的数据，序列号和图片是出口
     * @return 返回匹配和补录后的订单信息
     * @return 返回匹配和补录后的订单信息
     * @author lcy
     * @date 2021/8/28
     */
    @Override
    public Result<ParkOrderInfoVO> getParkOrderInfoVOResult(ReChargeVo reChargeVo) {
        //总费用
        //这里的车牌号是入场的，序列号和图片是出场的
        String outSerialNo = reChargeVo.getOutSerialNo();
        if (ObjectUtil.isNotEmpty(outSerialNo)) {
            reChargeVo.setSerialNo(outSerialNo);
        }
        Result<?> chargeFeeRe = chargeFeeService.outParkChargeFee(reChargeVo.getParkCode(), reChargeVo.getPlateNo(), reChargeVo.getSerialNo(), reChargeVo.getOssImgPath());
        log.info(String.valueOf(chargeFeeRe));
        //订单上传到云端
        try {
            ParkOrderInfo parkOrderInfos = new ParkOrderInfo();
            BeanUtils.copyProperties(chargeFeeRe.getResult(), parkOrderInfos);
            //订单信息的车牌改成出场的
            if (null != reChargeVo.getOutPlateNo()) {
                parkOrderInfos.setPlateNo(reChargeVo.getOutPlateNo());
            }
            log.info("计费信息同步云端:-{}", parkOrderInfos);
            String url = "";
            restTemplateUtils.post(url, parkOrderInfos, ResultVo.class);
        } catch (Exception e) {
            log.error("计费信息上传到云端上传失败", e);
        }
        return (Result<ParkOrderInfoVO>) chargeFeeRe;
    }

    /**
     * @author lcy
     * @date 2021/9/3
     * @Description 补录时生成的异常入场纪录
     */
    @Override
    public ParkCarEnterLog createAbnormalParkCarEnterLog(ParkArmInfo parkArmInfo, ReChargeVo reChargeVo) {
        //从识别数据库查到出场信息
        SdkIdentifyResultPushLog sdkIdentifyResultPushLog = this.findBySerialNoAndPlate(reChargeVo.getSerialNo(), reChargeVo.getParkCode(), reChargeVo.getPlateNo());
        //生成入场记录
        ParkCarEnterLog carEnterLog = new ParkCarEnterLog();
        //与正常出场做个标识，查询所有正常出场用
        carEnterLog.setRemark("补录场内");
        //补录场内
        carEnterLog.setEnterType(JddConstant.IntNumber.INT_THREE);
        carEnterLog.setIsTransfer(JddConstant.StringNumber.STRING_ZERO);
        carEnterLog.setId(SnowFlakeUtil.getId());
        carEnterLog.setPlateNo(sdkIdentifyResultPushLog.getLicense());
        carEnterLog.setParkCode(reChargeVo.getParkCode());
        carEnterLog.setParkName(parkArmInfo.getParkName());
        carEnterLog.setSerialNo(reChargeVo.getSerialNo());
        carEnterLog.setArmName(parkArmInfo.getArmName());
        //前端传来的区域id，用户自行选择的区域id
        if (null != reChargeVo.getAreaId()) {
            carEnterLog.setAreaId(reChargeVo.getAreaId());
        }

        //批次号
        carEnterLog.setBatchNumber(carEnterLog.getId());
        carEnterLog.setCarTypeNo(String.valueOf(sdkIdentifyResultPushLog.getNType()));
        carEnterLog.setCarEnterTime(reChargeVo.getEnterDate());
        carEnterLog.setImgPath(sdkIdentifyResultPushLog.getImg());
        if (null != sdkIdentifyResultPushLog.getOssImg()) {
            carEnterLog.setOssImgPath(sdkIdentifyResultPushLog.getOssImg());
        }
        carEnterLog.setIsOut(JddConstant.IsOutType.ISOUT_ONE);
        carEnterLog.setIpAddr(sdkIdentifyResultPushLog.getIpAddr());
        carEnterLog.setCarType(null != sdkIdentifyResultPushLog.getLicense() ? MoblieControllerUtils.HAVEPLATE : MoblieControllerUtils.NONEPLATE);
        carEnterLog.setCreateTime(new Date());
        //车辆类型信息
        LambdaQueryWrapper<ConfigCarType> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(ConfigCarType::getId, reChargeVo.getCarTypeId());
        List<ConfigCarType> carTypeList = carTypeService.list(queryWrapper);

        if (org.apache.commons.collections4.CollectionUtils.isNotEmpty(carTypeList)) {
            ConfigCarType configCarType = carTypeList.get(0);
            carEnterLog.setCarTypeId(configCarType.getId());
            carEnterLog.setCarTypeName(configCarType.getCarTypeName());
            carEnterLog.setCarNature(configCarType.getCarType());
        }
        carEnterLog.setCarTypeNo(String.valueOf(sdkIdentifyResultPushLog.getNType()));

        // 内部车判断
        ParkInnerInfo isInner = parkInnerListMapper.getIsInner(reChargeVo.getParkCode(), sdkIdentifyResultPushLog.getLicense());
        if (isInner != null) {
            carEnterLog.setCarTypeNo(String.valueOf(JddConstant.TemporaryCarType.TEMPORARY_CAR_INNER));
            ConfigCarType configCarType = this.getCarType(carEnterLog.getParkCode(), Integer.valueOf(carEnterLog.getCarTypeNo()));
            if (null != configCarType) {
                carEnterLog.setCarTypeId(configCarType.getId());
                carEnterLog.setCarTypeName(configCarType.getCarTypeName());
            }
        }
        //加上工作站名和岗亭人账户
        String workUser = parkWorkStationService.getStationOperationNameByParkArm(parkArmInfo);
        if (ObjectUtil.isNotEmpty(workUser)) {
            carEnterLog.setWorkName(parkArmInfo.getWorkStationName());
            carEnterLog.setSysUserName(workUser);
        }
        //保存数据库
        parkCarEnterLogMapper.insert(carEnterLog);
        //调用云端保存入场记录接口 如果失败重试三次 并返回云端保存结果
        String url = "";
        //上传云端 图片路径修改成oss图片路径
        carEnterLog.setImgPath(carEnterLog.getOssImgPath());
        //云端保存结果 成功200 失败 400
        String remoteResults = getRemoteResults(url, carEnterLog);
        //如果云端保存入场记录成功则修改场端出场记录 是否同步成功状态
        if (remoteResults.equals(JddConstant.SaveCloudType.SAVE_SUCCESS)) {
            parkCarEnterLogMapper.editSynchronousStatus(carEnterLog.getId(), remoteResults);
        }
        return carEnterLog;
    }

    /**
     * 获取识别类型对应的车辆类型
     */
    public ConfigCarType getCarType(String parkCode, int type) {
        QueryWrapper<ConfigCarType> configCarTypeQueryWrapper = new QueryWrapper<>();
        configCarTypeQueryWrapper.eq("park_code", parkCode);
        List<ConfigCarType> configCarTypeList = carTypeService.list(configCarTypeQueryWrapper);
        for (ConfigCarType configCarType : configCarTypeList) {
            configCarType.getCarTypeNo();
            List<String> list = Arrays.asList(configCarType.getCarTypeNo().split(","));
            if (list.contains(String.valueOf(type))) {
                return configCarType;
            }
        }
        return null;
    }

    /**
     * @author lcy
     * @date 2021/9/3
     * @Description 补录时生成的异常入场纪录
     */
    @Override
    public ParkCarEnterLog createAbnormalParkCarEnterLogNew(ParkArmInfo parkArmInfo, ReChargeVo reChargeVo) {
        //从识别数据库查到出场信息
        SdkIdentifyResultPushLog sdkIdentifyResultPushLog = this.findBySerialNoAndPlate(reChargeVo.getOutSerialNo(), reChargeVo.getParkCode(), reChargeVo.getPlateNo());
        //生成入场记录
        ParkCarEnterLog carEnterLog = new ParkCarEnterLog();
        //与正常出场做个标识，查询所有正常出场用
        carEnterLog.setRemark("补录场内");
        //补录场内
        carEnterLog.setEnterType(JddConstant.IntNumber.INT_THREE);
        carEnterLog.setIsTransfer(JddConstant.StringNumber.STRING_ZERO);
        carEnterLog.setId(SnowFlakeUtil.getId());
        carEnterLog.setParkCode(reChargeVo.getParkCode());
        carEnterLog.setParkName(parkArmInfo.getParkName());
        carEnterLog.setArmName(parkArmInfo.getArmName());
        carEnterLog.setPlateNo(reChargeVo.getPlateNo());
        carEnterLog.setSerialNo(parkArmInfo.getSerialNo());
        carEnterLog.setIpAddr(parkArmInfo.getIpAddr());
        //前端传来的区域id，用户自行选择的区域id
        if (null != reChargeVo.getAreaId()) {
            carEnterLog.setAreaId(reChargeVo.getAreaId());
        } else {
            carEnterLog.setAreaId(parkArmInfo.getTargetAreaId());
        }
        //批次号
        carEnterLog.setBatchNumber(carEnterLog.getId());
        carEnterLog.setCarEnterTime(reChargeVo.getEnterDate());
        carEnterLog.setIsOut(JddConstant.IsOutType.ISOUT_ONE);
        carEnterLog.setCreateTime(new Date());
        //车辆类型信息
        LambdaQueryWrapper<ConfigCarType> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.eq(ConfigCarType::getId, reChargeVo.getCarTypeId());
        List<ConfigCarType> carTypeList = carTypeService.list(queryWrapper);
        carEnterLog.setCarType("无牌车".equals(reChargeVo.getPlateNo()) ? MoblieControllerUtils.NONEPLATE : MoblieControllerUtils.HAVEPLATE);

        if (CollectionUtils.isNotEmpty(carTypeList)) {
            ConfigCarType configCarType = carTypeList.get(0);
            carEnterLog.setCarTypeId(configCarType.getId());
            carEnterLog.setCarTypeName(configCarType.getCarTypeName());
            carEnterLog.setCarNature(configCarType.getCarType());
            carEnterLog.setCarTypeNo("1");
        }
        // 内部车判断
        ParkInnerInfo isInner = parkInnerListMapper.getIsInner(reChargeVo.getParkCode(), reChargeVo.getPlateNo());
        if (isInner != null) {
            carEnterLog.setCarTypeNo(String.valueOf(JddConstant.TemporaryCarType.TEMPORARY_CAR_INNER));
            ConfigCarType configCarType = this.getCarType(carEnterLog.getParkCode(), Integer.valueOf(carEnterLog.getCarTypeNo()));
            if (null != configCarType) {
                carEnterLog.setCarTypeId(configCarType.getId());
                carEnterLog.setCarTypeName(configCarType.getCarTypeName());
            }
        }
        //加上工作站名和岗亭人账户
        String workUser = parkWorkStationService.getStationOperationNameByParkArm(parkArmInfo);
        if (ObjectUtil.isNotEmpty(workUser)) {
            carEnterLog.setWorkName(parkArmInfo.getWorkStationName());
            carEnterLog.setSysUserName(workUser);
        }

        if (ObjectUtil.isEmpty(sdkIdentifyResultPushLog)) {
            log.error("查询不到sdk识别结果信息ParkCode：{},PlateNo：{}", reChargeVo.getParkCode(), reChargeVo.getPlateNo());
        } else {
            carEnterLog.setImgPath(sdkIdentifyResultPushLog.getImg());
            carEnterLog.setOssImgPath(sdkIdentifyResultPushLog.getOssImg());
        }

        //保存数据库
        parkCarEnterLogMapper.insert(carEnterLog);
        //调用云端保存入场记录接口 如果失败重试三次 并返回云端保存结果
        String url = "";       //上传云端
        // 图片路径修改成oss图片路径
        carEnterLog.setImgPath(carEnterLog.getOssImgPath());
        //云端保存结果 成功200 失败 400
        String remoteResults = getRemoteResults(url, carEnterLog);
        //如果云端保存入场记录成功则修改场端出场记录 是否同步成功状态
        if (remoteResults.equals(JddConstant.SaveCloudType.SAVE_SUCCESS)) {
            parkCarEnterLogMapper.editSynchronousStatus(carEnterLog.getId(), remoteResults);
        }
        return carEnterLog;
    }

    /**
     * 车辆入场逻辑
     *
     * @param parkArmInfo           停车场出入口配置信息
     * @param identifyResultPushLog 车辆识别信息
     * @return boolean
     * @author jdd孙庆伟
     * @date 2021/9/3 11:14:29
     * @version 1.0
     */
    private boolean carEntrance(ParkArmInfo parkArmInfo, SdkIdentifyResultPushLog identifyResultPushLog) throws Exception {
        //序列号
        String serialNo = parkArmInfo.getSerialNo();
        //车牌号
        String plateNo = identifyResultPushLog.getLicense();
        //车辆类型
        String type = identifyResultPushLog.getNType().toString();
        //车场编号
        String parkCode = identifyResultPushLog.getParkCode();


        /*
         * 判断是否是无牌车
         */
        if (JddConstant.MqttRecResultPushLog.TYPE_ZERO.equals(type)) {
            log.info("无牌车，扫描二维码");
            //无牌车传至云端缓存
          /*  //代码保留
            parkCarEnterLogService.sendUnlicensedCarEnter(parkCode,serialNo,identifyResultPushLog.getOssImg());*/
            parkCarEnterLogService.sendUnlicensedCarEnter(parkCode, serialNo);
            //提醒扫码
            ledCallInterface.EntranceUnlicensedAll(serialNo);
            //用户扫码后走临时车入场逻辑
            return false;
        }
        /*
         * 军警车默认自动开闸
         */
        if (freeIf(type)) {
            log.info("军警车:{}", plateNo + "免费通行");
            //保存军警车入场记录并播报开闸
            return welcomeDeal(identifyResultPushLog, parkArmInfo, serialNo, plateNo, JddConstant.MqttRecResultPushLog.IS_WHITE_THREE);
        }
        /*
         * 白名单黑名单入场处理
         */
        ParkWhiteList parkWhiteList = whiteListMapper.findByPlateNo(plateNo, JddConstant.ParkWhiteListConstant.LICENSE_STATUS_Y);
        if (null != parkWhiteList) {
            log.info("白名单车辆:{}", plateNo + "进行黑白名单判定");
            //如果是白名单
            if (String.valueOf(parkWhiteList.getLicenseType()).equals(JddConstant.ParkWhiteListConstant.LICENSE_TYPE_ONE)) {
                //白名单的true代表可以走
                if (judgeWhite(parkWhiteList)) {
                    //白名单并未过期
                    return welcomeDeal(identifyResultPushLog, parkArmInfo, serialNo, plateNo, JddConstant.MqttRecResultPushLog.IS_WHITE_FOUR);
                } else {
                    //白名单过期,按照临时车计费
                    cacheUtil.put("parkToEnd", plateNo, JddConstant.OverdueType.WHITE_OVERDUE);
                    log.info("白名单车辆:{}", plateNo + "已过期将按照临时车判断");
                }
            } else {
                //黑名单的true代表就是黑名单，不可以走
                if (judgeWhite(parkWhiteList)) {
                    //如果是黑名单并且iFBroadcast为false 则限制入场
                    ledCallInterface.EntranceBlacklistAll(serialNo, plateNo);
                    log.info("黑名单车辆:{}", plateNo + "禁行");
                    return false;
                }
            }
        }
        /*
         * 月租车入场处理
         */
        List<ParkCarMonthly> parkCarMonthlyList = parkCarMonthlyMapper.getEntranceMonthlyInfo(parkArmInfo.getTargetAreaId(), plateNo, JddConstant.ParkWhiteListConstant.LICENSE_STATUS_Y);
        if (CollectionUtils.isNotEmpty(parkCarMonthlyList)) {
            log.info("月租车:{}", plateNo + "进入禁行判定");
            // 判断是否在禁止通行时段内
            Boolean ifData = ifPassage(serialNo, parkCarMonthlyList.get(0).getCarTypeId(), plateNo);
            if (!ifData) {
                log.info("判定禁止通行，禁行时段禁止入场");
                return false;
            }
            log.info("判定禁止通行，进行月租车入场逻辑");
            //如果月租车入场成功
            if (criteria(parkCarMonthlyList, parkArmInfo, identifyResultPushLog)) {
                return true;
            }
        }
        /*
         * 储值车入场处理
         */
        ParkCarStored parkCarStored = parkCarStoredMapper.getEntranceStored(plateNo, JddConstant.ParkWhiteListConstant.LICENSE_STATUS_Y);
        if (null != parkCarStored) {
            log.info("储值车:{}", plateNo + "进入禁行判定");
            Boolean ifData = ifPassage(serialNo, parkCarStored.getCarTypeId(), plateNo);
            if (!ifData) {
                log.info("判定禁止通行，禁行时段禁止入场");
                return false;
            }
            log.info("判定禁止通行，进行储值车入场逻辑");
            //进入储值车入场处理
            return carStored(parkArmInfo, identifyResultPushLog);
        }

        /*
         * 临时车入场处理
         */
        ParkInnerInfo isInner = parkInnerListMapper.getIsInner(parkArmInfo.getParkCode(), plateNo);
        // 内部车判断
        if (isInner != null) {
            identifyResultPushLog.setNType(JddConstant.TemporaryCarType.TEMPORARY_CAR_INNER);
            String carTypeId = getCarType(identifyResultPushLog.getNType());
            if (ifPassage(serialNo, carTypeId, plateNo)) {
                log.info("内部车入场，自动开闸");
                ledCallInterface.TemporaryEntranceInConfigAll(serialNo, plateNo);
                saveParkCarEnterLog(identifyResultPushLog, parkArmInfo, JddConstant.MqttRecResultPushLog.IS_WHITE_ZERO);
                return true;
            }
        }

        // 根据车辆编号、车牌类型匹配停车场的车辆类型Id
        String carTypeId = getCarType(Integer.parseInt(type));
        //如果禁行配置存在
        log.info("临时车:{}", plateNo + "进入禁行判定");
        Boolean ifData = ifPassage(serialNo, carTypeId, plateNo);
        //临时车在禁行时段
        if (ifData) {
            log.info("判断成功，车辆允许入场,进行临时车入场逻辑");
            //进入临时车处理逻辑
            return temporaryCar(parkArmInfo, identifyResultPushLog);
        }
        log.info("判断失败，临时车禁止入场");
        return false;
    }

    /*** 功能描述: 判断是否属于有效得白名单/黑名单
     * @Author: lcy
     * @Date: 2021/11/24
     */
    private boolean judgeWhite(ParkWhiteList parkWhiteList) throws ParseException {
        Boolean flag = false;
        //永久则直接返回
        if (ObjectUtil.isNotEmpty(parkWhiteList.getIsForever())) {
            if (JddConstant.StringNumber.STRING_ONE.equals(parkWhiteList.getIsForever())) {
                log.info("--------------该车辆为永久黑/白名单--------------");
                return true;
            }
        }
        //判断非永久时，是否还算白名单
        Calendar now = Calendar.getInstance();
        //一周第一天是否为星期天
        boolean isFirstSunday = (now.getFirstDayOfWeek() == Calendar.SUNDAY);
        //获取周几
        int weekDay = now.get(Calendar.DAY_OF_WEEK);
        //若一周第一天为星期天，则-1
        if (isFirstSunday) {
            weekDay = weekDay - 1;
            if (weekDay == 0) {
                weekDay = 7;
            }
        }
        //周几有值就判断
        if (ObjectUtil.isNotEmpty(parkWhiteList.getWeekDay())) {
            String[] weekDays = {"周日", "周一", "周二", "周三", "周四", "周五", "周六"};
            //白名单新加了个周几不算白名单，星期不包含也不算白名单
            if (parkWhiteList.getWeekDay().contains(weekDays[weekDay])) {
                log.info("--------------该车辆为黑/白名单，星期包含今天--------------");
                flag = true;
            } else {
                log.info("--------------该车辆为黑/白名单，星期不包含今天--------------");
                return false;
            }
        }

        //日期的判断
        if (ObjectUtil.isNotEmpty(parkWhiteList.getStartDate()) && ObjectUtil.isNotEmpty(parkWhiteList.getEndDate())) {
            //现在时间
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            String s = sdf.format(new Date());
            Date nowDate = sdf.parse(s);
            //开始与过期时间
            Date startDate = parkWhiteList.getStartDate();
            Date endDate = parkWhiteList.getEndDate();
            //判断日期
            //true代表在时间范围内
            if (isEffectiveDate(nowDate, startDate, endDate)) {
                log.info("--------------该车辆为黑/白名单，日期包含今天--------------");
                flag = true;
            } else {
                log.info("--------------该车辆为黑/白名单，日期不包含今天--------------");
                return false;
            }
        }
        //时间的判断
        if (ObjectUtil.isNotEmpty(parkWhiteList.getStartTime()) && ObjectUtil.isNotEmpty(parkWhiteList.getEndTime())) {
            SimpleDateFormat dayFormat = new SimpleDateFormat("HH:mm:ss");
            //现在时间
            String s = dayFormat.format(new Date());
            Date nowTime = dayFormat.parse(s);
            //开始与过期时间
            Date startTime = parkWhiteList.getStartTime();
            Date endTime = parkWhiteList.getEndTime();
            //判断日期
            //true代表在时间范围内
            if (isEffectiveDate(nowTime, startTime, endTime)) {
                log.info("--------------该车辆为黑/白名单，时间包含今天--------------");
                flag = true;
            } else {
                log.info("--------------该车辆为黑/白名单，时间不包含今天--------------");
                return false;
            }
        }
        return flag;
    }

    /**
     * 车场出场逻辑
     *
     * @param identifyResultPushLog 车辆识别信息
     * @return void
     * @author jdd孙庆伟
     * @date 2021/9/3 11:14:36
     * @version 1.0
     */
    private void outHandle(ParkArmInfo parkArmInfo, SdkIdentifyResultPushLog identifyResultPushLog) throws Exception {
        Map<String, Object> data = new HashMap<>(16);
        //序列号
        String serialNo = parkArmInfo.getSerialNo();
        //车牌号
        String plateNo = identifyResultPushLog.getLicense();
        //车辆类型
        String type = identifyResultPushLog.getNType().toString();
        //车场名称
        String parkName = parkArmInfo.getParkName();
        //车场编号
        String parkCode = parkArmInfo.getParkCode();
        /*
         * 无牌车出场处理
         */
        if (type.equals(JddConstant.MqttRecResultPushLog.TYPE_ZERO)) {
            //无牌车传至云端缓存
            // parkCarOutLogService.sendUnlicensedCarOut(parkCode,serialNo,identifyResultPushLog.getOssImg());
            parkCarOutLogService.sendUnlicensedCarOut(parkCode, serialNo);
            //场端调用云端删除前一辆车redis缓存
            log.info("无牌车出场，删除redis缓存结果:{}", redisDelete(serialNo));
            ledCallInterface.OutUnlicensedAll(serialNo);
            return;
        }
        /*
         * 军警车免费通行
         */
        if (freeIf(type)) {
            log.info("军警车:{}", plateNo + "免费通行");
            ledCallInterface.OutSpecialAll(serialNo, plateNo);
            //保存出场纪录
            handle(identifyResultPushLog, JddConstant.MqttRecResultPushLog.IS_WHITE_THREE);
            return;
        }
        /*
         * 白名单出场处理
         */
        ParkWhiteList isWhite = whiteListMapper.findByPlateNo(plateNo, JddConstant.ParkWhiteListConstant.LICENSE_STATUS_Y);
        if (null != isWhite) {
            log.info("白名单:{}", plateNo + "出场处理");
            if (JddConstant.ParkWhiteListConstant.LICENSE_TYPE_ONE.equals(String.valueOf(isWhite.getLicenseType()))) {
                //判断白名单有效期
                if (judgeWhite(isWhite)) {
                    log.info("白名单:{}", plateNo + "出场");
                    ledCallInterface.OutParkWhiteAll(serialNo, plateNo);
                    //保存出场纪录
                    handle(identifyResultPushLog, JddConstant.MqttRecResultPushLog.IS_WHITE_FOUR);
                    return;
                }
            }
        }

        /*
         * 收费车辆处理
         */
        Result<?> resultRe = null;
        resultRe = chargeFeeService.outParkChargeFee(parkCode, plateNo, serialNo, identifyResultPushLog.getOssImg());
        log.info("调用计费规则返回结果：{}", JSONObject.toJSONString(resultRe));

        //收费车辆处理订单详情
        ParkOrderInfoVO parkOrderInfo = (ParkOrderInfoVO) resultRe.getResult();
        //无异常 无入场记录不抬杆
        if (resultRe.getCode().equals(JddConstant.AdmissionRecordsCarType.NO_OPEN_CODE)) {
            String plateNoOfTen = cacheUtil.get(CURRENT_EXIT_LICENSE_PLATE_TEN, parkArmInfo.getSerialNo());
            if (ObjectUtil.isNotEmpty(plateNoOfTen) && plateNoOfTen.equals(plateNo)) {
                ledCallInterface.OutPayParkingFeeAll(serialNo, plateNo);
            } else {
                log.info("车牌:{}", plateNo + "无入场记录不抬杆");
                data = generateParkOrderInfoVO(parkArmInfo, identifyResultPushLog, type, plateNo, 0L);
                socketServer.sendInfo(JSONObject.toJSONString(data), parkArmInfo.getUsedUserId());
                ledCallInterface.OutAbnormalBanAll(serialNo, plateNo);
                log.info(JddConstant.AdmissionRecordsCarType.NO_OPEN_MESSAGE);
            }
            return;
        }
        //无异常 无入场记录免费放行
        if (resultRe.getCode().equals(JddConstant.AdmissionRecordsCarType.FREE_RELEASE_CODE)) {
            String plateNoOfTen = cacheUtil.get(CURRENT_EXIT_LICENSE_PLATE_TEN, parkArmInfo.getSerialNo());
            if (ObjectUtil.isNotEmpty(plateNoOfTen) && plateNoOfTen.equals(plateNo)) {
                ledCallInterface.OutPayParkingFeeAll(serialNo, plateNo);
            } else {
                log.info("车牌:{}", plateNo + "无入场记录免费放行");
                ledCallInterface.OutAbnormalCanAll(serialNo, plateNo);
                log.info(JddConstant.AdmissionRecordsCarType.FREE_RELEASE_MESSAGE);
            }
            return;
        }

        //调用计费规则异常处理
        if (200 != resultRe.getCode()) {
            ledCallInterface.OutAbnormalAll(serialNo, plateNo);
            log.info("未识别到车辆信息 - code -200");
            return;
        }


        //停车费已支付处理
        if (parkOrderInfo.getChargeUnpaid().equals(JddConstant.StringNumber.STRING_ZERO)) {
            if (parkOrderInfo.getChargeTotalFee() != null && !JddConstant.StringNumber.STRING_ZERO.equals(parkOrderInfo.getChargeTotalFee())) {
                //场内码支付完的
                ledCallInterface.FieldFeeAll(serialNo, plateNo);
            } else {
                ledCallInterface.OutPayParkingFeeAll(serialNo, plateNo);
            }
            cacheUtil.put(CURRENT_EXIT_LICENSE_PLATE_TEN, parkArmInfo.getSerialNo(), plateNo);
            handle(identifyResultPushLog, JddConstant.MqttRecResultPushLog.IS_WHITE_ZERO);
            try {
                //临时车扫场内码付过钱和月租车，更新交接班数据
                doHandUpdate(parkArmInfo, plateNo, parkOrderInfo);
            } catch (Exception e) {
                log.error("---------停车费已支付处理失败----------");
            }
            return;
        }
        /*
         * 优惠卷处理
         */
        ParkCarStored parkCarStored = parkCarStoredMapper.getEntranceStored(plateNo, "Y");
        if (null == parkCarStored) {
            log.info("车牌:{}", plateNo + "查询可用优惠卷");
            //组装参数
            MultiValueMap<String, String> request = new LinkedMultiValueMap<>();
            request.add("parkCode", parkCode);
            request.add("plateNo", plateNo);
            //停车时常
            request.add("elapsedTime", String.valueOf(parkOrderInfo.getChargeDuration()));
            HttpHeaders headers = new HttpHeaders();
            // 以表单的方式提交
            headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
            HttpEntity<MultiValueMap<String, String>> requests = new HttpEntity<>(request, headers);
            String url = "";
            ResponseEntity<ResultVo> posts = restTemplateUtils.post(url, requests, ResultVo.class);
            ResultVo body = posts.getBody();

            if (null != body && body.getCode() == 200 && (StringUtils.isNotBlank(String.valueOf(body.getData())) && !"null".equals(body.getData()))) {
                String discountCouponDetailStr = (String) body.getData();
                //查询的优惠卷免费时常大于停车时常
                DiscountCouponDetail discountCouponDetail = JSONObject.parseObject(discountCouponDetailStr, DiscountCouponDetail.class);
                log.info("车牌:{},可用优惠卷:{}", plateNo, discountCouponDetail.getCouponName());
                ledCallInterface.OutDiscountAll(serialNo, plateNo, discountCouponDetail);
                //保存出场记录
                handle(identifyResultPushLog, JddConstant.MqttRecResultPushLog.IS_WHITE_ZERO);
                //保存使用免费时长券/全免券/小时券的订单信息
                saveParkOrderInfo(discountCouponDetail, parkOrderInfo, parkArmInfo);
                return;
            }
        }
        /*
         * 储值车出场处理
         */
        if (null != parkCarStored) {
            log.info("储值车:{}", plateNo + "出场处理");
            //余额
            Integer payFee = parkCarStored.getPayFee();
            //本次金额
            Integer unpayFee = Integer.parseInt(parkOrderInfo.getChargeUnpaid()) / 100;
            if (payFee >= unpayFee) {
                log.info("储值车:{},余额大于本次费用-----余额:{},本次费用:{}", plateNo, payFee, unpayFee);
                //支付费用-本次金额=剩余金额
                int remaining = payFee - unpayFee;
                log.info("储值车出场:{},支付后余额为:{}", plateNo, remaining);
                ledCallInterface.OutStoredPayFeeAll(serialNo, plateNo, unpayFee, remaining, parkOrderInfo);

                //保存订单
                ParkOrderInfo orderInfo = new ParkOrderInfo();
                orderInfo.setCarEnterId(parkOrderInfo.getCarEnterId());
                orderInfo.setEnterTime(parkOrderInfo.getEnterTime());
                orderInfo.setChargeTotalFee(parkOrderInfo.getChargeUnpaid());
                orderInfo.setOutTime(DateUtils.formatDateTime());
                orderInfo.setId(SnowFlakeUtil.getId());
                orderInfo.setCreateTime(new Date());
                orderInfo.setParkCode(parkCode);
                orderInfo.setParkName(parkName);
                orderInfo.setSerialNo(serialNo);
                orderInfo.setPlateNo(plateNo);
                orderInfo.setPayTime(DateUtils.formatDateTime());
                orderInfo.setChargeDuration(parkOrderInfo.getChargeDuration());
                orderInfo.setCarType("1");
                orderInfo.setChargeUnpaid(parkOrderInfo.getChargeUnpaid());
                orderInfo.setChargePaid(String.valueOf(parkOrderInfo.getChargeUnpaid()));
                orderInfo.setOrderStatus("2");
                orderInfo.setPayType("5");
                orderInfoMapper.insert(orderInfo);
                try {
                    //加上工作站名和岗亭人账户
                    String workUser = parkWorkStationService.getStationOperationNameByParkArm(parkArmInfo);
                    if (ObjectUtil.isNotEmpty(workUser)) {
                        orderInfo.setWorkName(parkArmInfo.getWorkStationName());
                        orderInfo.setSysUserName(workUser);
                        //交接班更新,储值车这里只更新钱数
                        orderInfo.setChargeDue(orderInfo.getChargeTotalFee());
                        parkHandOverLogService.updateHandOverAuto(null, orderInfo, workUser, 0);
                    }
                } catch (Exception e) {
                    log.error("-----------------车辆出场交接班更新或者发送场内车辆详情失败----------------");
                }
                //更新储值车余额
                parkCarStored.setPayFee(remaining);
                parkCarStoredMapper.updateById(parkCarStored);
                //交接班更新，储值车这里只更新出场数
                handle(identifyResultPushLog, JddConstant.MqttRecResultPushLog.IS_WHITE_TWO);
                return;
            }
        }
        //webSocket发送订单信息
        data.put("parkOrderInfo", parkOrderInfo);
        socketServer.sendInfo(JSONObject.toJSONString(data), parkArmInfo.getUsedUserId());
        //临时车出场、或储值车余额不足出场
        log.info("临时车:{},金额:{}", plateNo, Integer.parseInt(parkOrderInfo.getChargeUnpaid()) / 100);
        ledCallInterface.OutShowFeeAll(serialNo, plateNo, parkOrderInfo);

        //计费信息同步发送云端
        try {
            ParkOrderInfo parkOrderInfos = new ParkOrderInfo();
            BeanUtils.copyProperties(parkOrderInfo, parkOrderInfos);
            log.info("计费信息同步云端:-{}", parkOrderInfos);
            String url = "";
            ResponseEntity<ResultVo> post = restTemplateUtils.post(url, parkOrderInfos, ResultVo.class);
            log.info("计费信息同步云端结果:-{}", JSONObject.toJSONString(post));
        } catch (Exception e) {
            log.error("计费信息上传到云端上传失败", e);
        }
    }

    /**
     * 功能描述: 处理临时车扫过场内码，月租车，直接出场的情况
     *
     * @Author: lcy
     * @Date: 2021/12/2
     */
    private void doHandUpdate(ParkArmInfo parkArmInfo, String plateNo, ParkOrderInfoVO parkOrderInfo) {
        String workUser = parkWorkStationService.getStationOperationNameByParkArm(parkArmInfo);
        ParkOrderInfo HandOrderInfo = new ParkOrderInfo();
        BeanUtils.copyProperties(parkOrderInfo, HandOrderInfo);
        HandOrderInfo.setOrderStatus("2");
        // 多次扫过场内码没扫出口码得
        // 判断订单是否有扫场内码的
        List<ParkOrderInfo> orderByCarEnterIds = parkOrderInfoMapper.getOrderByCarEnterId(parkOrderInfo.getCarEnterId());
        if (ObjectUtil.isNotEmpty(orderByCarEnterIds)) {
            List<ParkOrderInfo> parkOrderInfos = orderByCarEnterIds.stream().filter(item -> {
                //6表示线下收费，这里要查所有线上收费的
                return !JddConstant.StringNumber.STRING_SIX.equals(item.getPayType()) && !JddConstant.StringNumber.STRING_FIVE.equals(item.getPayType());
            }).collect(Collectors.toList());
            //这里表示有扫场内码的
            if (ObjectUtil.isNotEmpty(parkOrderInfos) && parkOrderInfos.size() > 0) {
                //走到这里说明是场内码支付的
                //paid+couponamt是应收
                BigDecimal chargeDue = new BigDecimal(0);
                BigDecimal chargePaid = new BigDecimal(0);
                BigDecimal couponAmt = new BigDecimal(0);
                for (ParkOrderInfo orderInfo : parkOrderInfos) {
                    chargePaid = chargePaid.add(new BigDecimal(null == orderInfo.getChargePaid() ? "0" : orderInfo.getChargePaid()));
                    couponAmt = couponAmt.add(new BigDecimal(null == orderInfo.getCouponAmt() ? "0" : orderInfo.getCouponAmt()));
                    chargeDue = couponAmt.add(chargePaid);
                    //场内码赋值上谁收的钱
                    orderInfo.setSysUserName(workUser);
                    parkOrderInfoMapper.updateById(orderInfo);
                }
                //如果场内码产生费用了再更新，没产生费用不更新
                if (!"0".equals(chargePaid.toString()) || !"0".equals(couponAmt.toString()) || !"0".equals(chargeDue.toString())) {
                    //这里的订单数字在updateInParkNum处理，这里不用处理
                    //场内码更新
                    HandOrderInfo.setChargePaid(chargePaid.toString());
                    HandOrderInfo.setChargeDue(chargeDue.toString());
                    HandOrderInfo.setCouponAmt(couponAmt.toString());
                    //单独的处理扫场内码后直接出来的情况
                    updateInParkNum(HandOrderInfo, workUser);
                    return;
                }
            }
        }
        //临时车不收费时的处理逻辑
        if (JddConstant.StringNumber.STRING_ZERO.equals(parkOrderInfo.getChargeTotalFee())) {
            //避免月租车重复数据
            List<ParkCarMonthly> entranceMonthlyInfo = parkCarMonthlyMapper.getEntranceMonthlyInfo(parkArmInfo.getSourceAreaId(), plateNo, JddConstant.StatusType.STATUS_Y);
            //如果是月租车就不继续，因为月租车已经在上面处理过了
            if (ObjectUtil.isNotEmpty(entranceMonthlyInfo)) {
                return;
            }
            //  处理临时车免费的逻辑，自动放行加1
            ParkHandOverLog hand = parkHandOverLogMapper.getHand(workUser);
            hand.setNormalNoFeeOut(hand.getNormalNoFeeOut() + 1);
            parkHandOverLogMapper.updateById(hand);
        }
    }

    /**
     * 功能描述: 单独的处理扫场内码后直接出来的情况
     *
     * @Author: lcy
     * @Date: 2021/12/3
     */
    private void updateInParkNum(ParkOrderInfo handOrderInfo, String workUser) {
        ParkHandOverLog hand = parkHandOverLogMapper.getHand(workUser);
        if (ObjectUtil.isEmpty(hand)) {
            log.error("-------单独的处理扫场内码后直接出来失败------------");
            return;
        }
        BigDecimal receivable = BigDecimal.ZERO;
        BigDecimal paid = BigDecimal.ZERO;
        BigDecimal free = BigDecimal.ZERO;
        //应收
        if (ObjectUtil.isNotEmpty(handOrderInfo.getChargeDue())) {
            receivable = new BigDecimal(handOrderInfo.getChargeDue()).divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_HALF_UP);
        }
        //实收
        if (ObjectUtil.isNotEmpty(handOrderInfo.getChargePaid())) {
            paid = new BigDecimal(handOrderInfo.getChargePaid()).divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_HALF_UP);
        }
        //免费
        if (ObjectUtil.isNotEmpty(handOrderInfo.getCouponAmt())) {
            free = new BigDecimal(handOrderInfo.getCouponAmt()).divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_HALF_UP);
            //使用优惠卷时计算实际收费
            if (ObjectUtil.isNotEmpty(handOrderInfo.getChargePaid())) {
                paid = receivable.subtract(free);
            }
        }
        //线上收费：扫码和储值
        hand.setOnlineReceivable(hand.getOnlineReceivable().add(receivable));
        hand.setOnlinePaid(hand.getOnlinePaid().add(paid));
        hand.setOnlineFree(hand.getOnlineFree().add(free));
        //判断正常收费还是正常免费
        if (JddConstant.StringNumber.STRING_ZERO.equals(handOrderInfo.getChargePaid()) || handOrderInfo.getChargePaid().equals(handOrderInfo.getCouponAmt())) {
            //实际付款0或者优惠卷等于实付则属于正常免费
            hand.setNormalFeeOut(hand.getNormalNoFeeOut() + 1);
        } else {
            //属于正常收费
            hand.setNormalFeeOut(hand.getNormalFeeOut() + 1);
        }
        parkHandOverLogMapper.updateById(hand);
        log.info("-----------更新场内码支付出场成功-----------");
    }

    /**
     * @param sdkIdentifyResultPushLog 识别结构体
     * @return
     * @Description 车辆出场记录保存
     * @author shangyue
     * @Date 2021/4/7
     */
    private void handle(SdkIdentifyResultPushLog sdkIdentifyResultPushLog, String carNature) throws IOException {
        String plateNo = sdkIdentifyResultPushLog.getLicense();
        String serialNo = sdkIdentifyResultPushLog.getSerialNo();
        ParkCarOutLog parkCarOutLog = new ParkCarOutLog();
        //出入口配置信息
        ParkArmInfo parkArmInfo = parkArmInfoMapper.getParkArmBySerialNo(sdkIdentifyResultPushLog.getSerialNo());

        //得到最新的一次入场记录
        ParkCarEnterLog carEnterLog = parkCarEnterLogService.getLastCarEnterLog(sdkIdentifyResultPushLog.getParkCode(), plateNo, JddConstant.ParkCarEnterLog.CAR_NO_OUT);
        if (StringUtils.isNotBlank(carEnterLog.getParkNumber())) {
            parkSpaceService.updateParkStatus(carEnterLog, JddConstant.ParkSpace.PARKSTATUS_ZERO);

            RestTemplateSpaceVo restTemplateSpaceVo = new RestTemplateSpaceVo();
            restTemplateSpaceVo.setLatestParkCarEnterLogStr(JSONObject.toJSONString(carEnterLog));
            log.info("发送请求云端同步更新月租车车位停靠状态:{}", sendCloudSynSpace(restTemplateSpaceVo));
            //月租车处理，后面判断是否自动放行用
            carNature = JddConstant.MqttRecResultPushLog.IS_WHITE_ONE;
        }
        parkCarOutLog.setId(SnowFlakeUtil.getId());
        parkCarOutLog.setParkCode(parkArmInfo.getParkCode());
        parkCarOutLog.setParkName(parkArmInfo.getParkName());
        parkCarOutLog.setPlateNo(plateNo);
        parkCarOutLog.setCarEnterId(carEnterLog.getId());
        parkCarOutLog.setSerialNo(serialNo);
        parkCarOutLog.setCarNature(carNature);
        parkCarOutLog.setCarType(String.valueOf(JddConstant.IntNumber.INT_ONE));
        parkCarOutLog.setCarOutTime(DateUtil.getTime());
        parkCarOutLog.setArmName(parkArmInfo.getArmName());
        parkCarOutLog.setImgPath(sdkIdentifyResultPushLog.getImg());
        parkCarOutLog.setOssImgPath(sdkIdentifyResultPushLog.getOssImg());
        parkCarOutLog.setIpAddr(parkArmInfo.getIpAddr());
        parkCarOutLog.setCreateTime(new Date());
        parkCarOutLog.setSynchronousStatus(JddConstant.StatusType.STATUS_N);
        String workUser = parkWorkStationService.getStationOperationNameByParkArm(parkArmInfo);
        try {
            //加上工作站名和岗亭人账户
            if (ObjectUtil.isNotEmpty(workUser)) {
                parkCarOutLog.setWorkName(parkArmInfo.getWorkStationName());
                parkCarOutLog.setSysUserName(workUser);
                //交接班更新，这里只更新出场数
                parkHandOverLogService.updateHandOverAuto(parkCarOutLog, null, workUser, 0);
            }
        } catch (Exception e) {
            log.error("-----------------车辆出场交接班更新或者发送场内车辆详情失败----------------");
        }
        parkCarOutLogMapper.insert(parkCarOutLog);
        parkCarEnterLogMapper.updateIsOut(plateNo);
        Map<String, Object> data = parkArmInfoService.getData(workUser);
        socketServer.sendInfo(JSONObject.toJSONString(data), parkArmInfo.getUsedUserId());
        //调用云端保存出场记录接口 如果失败重试三次 并返回云端保存结果
        String url = "";
        //上传云端 图片路径修改成oss图片路径
        parkCarOutLog.setImgPath(parkCarOutLog.getOssImgPath());
        //云端保存结果 200：成功 400：失败
        String remoteResults = getRemoteResults(url, parkCarOutLog);
        //如果云端保存出场记录成功则修改场端出场记录 是否同步成功状态
        if (remoteResults.equals(JddConstant.SaveCloudType.SAVE_SUCCESS)) {
            parkCarOutLogMapper.editSynchronousStatus(parkCarOutLog.getId(), remoteResults);
        }
    }

    /**
     * 保存停车订单
     */
    private void saveParkOrderInfo(DiscountCouponDetail discountCouponDetail, ParkOrderInfoVO parkGetCarFeeLog, ParkArmInfo parkArmInfo) throws IOException {
        log.info("使用全免/免费通行券保存订单：券详情{},计费信息{}", JSONObject.toJSONString(discountCouponDetail), JSONObject.toJSONString(parkGetCarFeeLog));
        ParkOrderInfo parkOrderInfo = new ParkOrderInfo();
        // 费用
        BeanUtils.copyProperties(parkGetCarFeeLog, parkOrderInfo);
        parkOrderInfo.setPlateNo(parkGetCarFeeLog.getPlateNo());
        parkOrderInfo.setParkCode(parkGetCarFeeLog.getParkCode());
        //  进场时间
        parkOrderInfo.setEnterTime(parkGetCarFeeLog.getEnterTime());
        // 出场时间
        parkOrderInfo.setOutTime(DateUtils.dateToString(new Date(), "yyyy-MM-dd HH:mm:ss"));
        parkOrderInfo.setOrderStatus("2");
        parkOrderInfo.setOrderStatusDesc("使用免费时长券订单或全免券,discount_type{}" + discountCouponDetail.getDiscountType());
        parkOrderInfo.setParkName(parkGetCarFeeLog.getParkName());
        parkOrderInfo.setVersion(0);
        parkOrderInfo.setPayTime(DateUtils.dateToString(new Date(), "yyyyMMddHHmmss"));
        parkOrderInfo.setSourceType("2");
        parkOrderInfo.setOutTradeNo(null);
        parkOrderInfo.setBankDiscAmt("0");
        parkOrderInfo.setOrderId(SnowFlakeUtil.getId());
        parkOrderInfo.setMerId(null);
        parkOrderInfo.setPayType("4");
        parkOrderInfo.setCreateTime(new Date());
        parkOrderInfo.setPaymentAmt("0");
        /*由于云端校验了优惠卷，进入该方法的都是可以使用优惠卷直接出场的车辆,所以优惠卷金额使用支付费用*/
        //优惠券金额
        parkOrderInfo.setCouponAmt(parkGetCarFeeLog.getChargeUnpaid());
        //原停车费用
        parkOrderInfo.setChargeDue(parkGetCarFeeLog.getChargeUnpaid());
        //已支付费用
        parkOrderInfo.setChargePaid(parkGetCarFeeLog.getChargeUnpaid());
        //订单总金额
        parkOrderInfo.setTotalAmt(parkGetCarFeeLog.getChargeUnpaid());
        //优惠立减金额
        parkOrderInfo.setMerDiscAmt(parkGetCarFeeLog.getChargeUnpaid());
        parkOrderInfo.setCouponUseFlag(JddConstant.DiscountCouponConstant.USAGE_STATUS_USED);
        /*使用优惠券*/
        parkOrderInfo.setCouponId(discountCouponDetail.getId());
        try {
            //加上工作站名和岗亭人账户
            String workUser = parkWorkStationService.getStationOperationNameByParkArm(parkArmInfo);
            if (ObjectUtil.isNotEmpty(workUser)) {
                //交接班更新,这里只更新钱数
                parkHandOverLogService.updateHandOverAuto(null, parkOrderInfo, workUser, 0);
//                JSONObject HandOverLog = new JSONObject();
//                HandOverLog.put("HandOverLog", parkHandOverLog);
//                socketServer.sendInfo(HandOverLog.toJSONString(), parkArmInfo.getUsedUserId());
                Map<String, Object> data = parkArmInfoService.getData(workUser);
                socketServer.sendMessageAll(JSONObject.toJSONString(data));
            }
        } catch (Exception e) {
            log.error("-----------------车辆出场交接班更新或者发送场内车辆详情失败----------------");
        }
        if (JddConstant.DiscountCouponConstant.COUPON_TYPE_ORDINARY == discountCouponDetail.getCouponType()) {
            String url = "";
            restTemplateUtils.post(url, discountCouponDetail, ResultVo.class);
        }
        log.info("使用优惠券保存订单：{}", JSONObject.toJSONString(parkOrderInfo));
        parkOrderInfoMapper.insert(parkOrderInfo);
        //使用优惠卷出场的订单发送到云端 用于微信推送出场纪录显示优惠金额
        String url = "";
        restTemplateUtils.post(url, parkOrderInfo, ResultVo.class);
    }

    /**
     * 场端调用云端删除前一辆车redis缓存
     *
     * @param serialNo
     * @return com.jdd.common.api.vo.ResultVo
     * @author jdd孙庆伟
     * @date 2021/9/6 11:11:03
     * @version 1.0
     */
    public ResultVo redisDelete(String serialNo) {
        String url = "";
        RedisVo redisVo = new RedisVo();
        redisVo.setSerialNo(serialNo);
        redisVo.setKey("park:cloud:out:fee:");
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        //封装参数
        MultiValueMap<String, String> stringStringSortedMap = EncapsulationSignDataUtils.encapsulationParameters(redisVo, appId, appScrect);
        // 组装请求体
        HttpEntity<MultiValueMap<String, String>> request =
                new HttpEntity<MultiValueMap<String, String>>(stringStringSortedMap, headers);
        ResponseEntity<ResultVo> cmsResultDTO = restTemplateUtils.post(url, request, ResultVo.class);
        return cmsResultDTO.getBody();
    }

    /**
     * 临时车入场处理
     *
     * @param parkArmInfo              车场信息
     * @param sdkIdentifyResultPushLog 车辆入场识别信息
     */
    public boolean temporaryCar(ParkArmInfo parkArmInfo, SdkIdentifyResultPushLog sdkIdentifyResultPushLog) throws Exception {
        String serialNo = parkArmInfo.getSerialNo();
        String plateNo = sdkIdentifyResultPushLog.getLicense();
        //获取车场配置信息
        ConfigParkInfo configParkInfo = configParkInfoService.findConfigParkInfoByParkCode();
        //如果车厂配置信息不为空
        if (null != configParkInfo) {
            //车场临时车位数
            Integer temporaryParking = configParkInfo.getTemporaryParking();
            Integer noOutCarNumber = parkCarEnterLogMapper.getNoOutCarNumber();
            //临时车位已满情况处理
            if (noOutCarNumber > temporaryParking || temporaryParking.equals(noOutCarNumber)) {
                //临时车位满位禁止通行 1：开启
                if (JddConstant.ConfigParkInfo.TEMPORARY_STALL_TYPE_ONE.equals(configParkInfo.getTemporaryStallType())) {
                    log.info("临时车位已满，禁止驶入");
                    ledCallInterface.TemporaryEntranceParkingFullAll(serialNo, plateNo);
                } else {
                    log.info("临时车位已满，确认开闸");
                    //临时车位满位禁止通行 0：关闭  如果临时车位已满 未配置已满禁行 则发送webSocket消息到页面 页面确认开闸
                    sendInfo(serialNo, plateNo, parkArmInfo, JddConstant.VehicleType.TEMPORARYCAR, "1", sdkIdentifyResultPushLog.getImg());
                    ledCallInterface.TemporaryEntranceFullConfirmOpenedAll(serialNo, plateNo);
                }
            } else {
                temporaryVehicle(serialNo, plateNo, configParkInfo, sdkIdentifyResultPushLog, parkArmInfo);
            }
        } else {
            log.info("车辆信息配置为空，请检查ConfigParkInfo配置是否存在");
        }
        return true;
    }

    /**
     * 临时车入场判断条件
     *
     * @param serialNo                 设备序列号
     * @param plateNo                  车牌号
     * @param configParkInfo           车场配置
     * @param sdkIdentifyResultPushLog 车辆识别结果
     * @param parkArmInfo              车道信息
     */
    private boolean temporaryVehicle(String serialNo, String plateNo, ConfigParkInfo configParkInfo, SdkIdentifyResultPushLog sdkIdentifyResultPushLog, ParkArmInfo parkArmInfo) throws Exception {
        /*
         *临时车入场开闸方式 1 自动开闸
         */
        if (configParkInfo.getTemporaryGateIn().equals(JddConstant.ConfigParkInfo.TEMPORARY_GATE_IN_ONE)) {
            log.info("临时车入场，自动开闸");
            ledCallInterface.TemporaryEntranceInConfigAll(serialNo, plateNo);
            saveParkCarEnterLog(sdkIdentifyResultPushLog, parkArmInfo, JddConstant.MqttRecResultPushLog.IS_WHITE_ZERO);
            return true;
        }
        /*
         *临时车入场开闸方式 2 确认开闸
         */
        if (configParkInfo.getTemporaryGateIn().equals(JddConstant.ConfigParkInfo.TEMPORARY_GATE_IN_TWO)) {
            log.info("临时车入场，确认开闸");
            ledCallInterface.TemporaryInConfigAll(serialNo, plateNo);
            //发送webSockt消息到前端
            sendInfo(serialNo, plateNo, parkArmInfo, JddConstant.VehicleType.TEMPORARYCAR, "0", sdkIdentifyResultPushLog.getImg());
            return true;
        }
        /*
         *临时车入场开闸方式 3、不开闸
         */
        if (configParkInfo.getTemporaryGateIn().equals(JddConstant.ConfigParkInfo.TEMPORARY_GATE_IN_THREE)) {
            log.info("临时车入场，不开闸");
            ledCallInterface.TemporaryBanEntranceAll(serialNo, plateNo);
            return true;
        }
        return false;
    }

    /**
     * 月租车入场逻辑
     *
     * @param parkCarMonthlyList       月租车信息列表
     * @param parkArmInfo              车场信息
     * @param sdkIdentifyResultPushLog 车场入场识别结果
     * @throws Exception
     */
    private boolean criteria(List<ParkCarMonthly> parkCarMonthlyList, ParkArmInfo parkArmInfo, SdkIdentifyResultPushLog sdkIdentifyResultPushLog) throws Exception {
        String serialNo = parkArmInfo.getSerialNo();
        String plateNo = sdkIdentifyResultPushLog.getLicense();
        ConfigParkInfo configParkInfo = configParkInfoService.findConfigParkInfoByParkCode();
        // 检测开闸配置是否存在
        if (null != configParkInfo) {
            // 从该车牌号的所有的月租车信息中，得到所有的未过期的月租车信息
            List<ParkCarMonthly> noOverTimeInfoList = parkCarMonthlyList.stream().filter(car -> car.getEndTime().getTime() > System.currentTimeMillis()).collect(Collectors.toList());
            // 如果存在未过期的月租车信息
            if (noOverTimeInfoList.size() > 0) {
                log.info("月租车，未过期进行车位判断");
                //查询当前车牌未出场的入场记录
                ParkCarEnterLog latestParkCarEnterLog = parkCarEnterLogService.getLastCarEnterLog(sdkIdentifyResultPushLog.getParkCode(), plateNo, JddConstant.StringNumber.STRING_ZERO);
                String parkNumber = null;
                if (null != latestParkCarEnterLog && latestParkCarEnterLog.getAreaId().equals(parkArmInfo.getTargetAreaId())) {
                    // 判断车辆是否重复入场
                    parkNumber = latestParkCarEnterLog.getParkNumber();
                    log.info("月租车重复入场，从入场记录中重复获取车位号:{}", parkNumber);
                }

                //如果月租车车位为空
                if (StringUtils.isBlank(parkNumber)) {
                    log.info("月租车未得到车位号");
                    //在这里查看自己的车位是否被占用,为null的话可能属于车位用过了，就该当成临时车了
                    parkNumber = getMonthLeisureParkNumber(parkArmInfo.getTargetAreaId(), parkCarMonthlyList);
                    log.info("月租车尝试获取车位号，获取车位号：{}", parkNumber);
                }

                //如果月租车车位不为空
                //为空就不走下面逻辑，就会当成临时车
                if (StringUtils.isNotBlank(parkNumber)) {
                    log.info("月租车车位号存在:{}", parkNumber);
                    // 按月租车入场
                    ledCallInterface.MonthlyEntranceAll(serialNo, plateNo, null);
                    //组装月租车入场记录
                    ParkCarEnterLog parkCarEnterLog = packageCarEnterLog(sdkIdentifyResultPushLog, parkArmInfo, new ParkCarEnterLog(), DateUtil.newDate(), JddConstant.MqttRecResultPushLog.IS_WHITE_ONE);
                    parkCarEnterLog.setParkNumber(parkNumber);
                    if (StringUtils.isBlank(parkArmInfo.getSourceAreaId())) {
                        parkCarEnterLogMapper.updateIsOut(sdkIdentifyResultPushLog.getLicense());
                        parkCarEnterLog.setBatchNumber(parkCarEnterLog.getId());
                    } else {
                        if (null != latestParkCarEnterLog) {
                            parkCarEnterLog.setBatchNumber(latestParkCarEnterLog.getBatchNumber());
                        }
                    }
                    //保存月租车入场记录并上传云端
                    addParkCarMonthlyEnterLog(parkCarEnterLog, latestParkCarEnterLog);
                    log.info("月租车:{}", plateNo + "可以通行");
                    return true;
                }
                return false;
            } else {
                //月组车过期处理逻辑
                return parkCarMonthlyOverTime(sdkIdentifyResultPushLog, parkArmInfo, configParkInfo, parkCarMonthlyList.get(0));
            }
        }
        log.info("车场开闸方式不存在");
        return false;
    }

    /**
     * 储值车入场处理
     *
     * @param parkArmInfo              出入口配置信息
     * @param sdkIdentifyResultPushLog 相机识别结果
     * @return boolean
     * @author jdd孙庆伟
     * @date 2021/9/6 10:18:29
     * @version 1.0
     */
    private boolean carStored(ParkArmInfo parkArmInfo, SdkIdentifyResultPushLog sdkIdentifyResultPushLog) throws Exception {
        String serialNo = parkArmInfo.getSerialNo();
        String plateNo = sdkIdentifyResultPushLog.getLicense();
        ConfigParkInfo configParkInfo = configParkInfoService.findConfigParkInfoByParkCode();
        if (configParkInfo != null) {
            //自动开闸情况
            if (configParkInfo.getMonthlyRentalGateIn().equals(JddConstant.ConfigParkInfo.MONTHLY_RENTAL_GATE_IN_ONE)) {
                log.info("储值车:{}", plateNo + "入场自动开闸");
                ledCallInterface.EntranceStoredInConfigAll(serialNo, plateNo);
                saveParkCarEnterLog(sdkIdentifyResultPushLog, parkArmInfo, JddConstant.MqttRecResultPushLog.IS_WHITE_TWO);
                return true;
            }
            //确认开闸
            if (configParkInfo.getMonthlyRentalGateIn().equals(JddConstant.ConfigParkInfo.MONTHLY_RENTAL_GATE_IN_TWO)) {
                log.info("储值车:{}", plateNo + "入场确认开闸");
                //发送webSockt消息到前端
                sendInfo(sdkIdentifyResultPushLog.getSerialNo(), sdkIdentifyResultPushLog.getLicense(), parkArmInfo, JddConstant.VehicleType.STOREDCAR, "0", sdkIdentifyResultPushLog.getImg());
                ledCallInterface.StoredInConfigAll(serialNo, plateNo);
                return true;
            }
        }
        log.info("储值车:{}", plateNo + "入场成功");
        ledCallInterface.EntranceStoredNoConfigAll(serialNo, plateNo);
        saveParkCarEnterLog(sdkIdentifyResultPushLog, parkArmInfo, JddConstant.MqttRecResultPushLog.IS_WHITE_TWO);
        return true;
    }

    /**
     * 月租车过期处理
     *
     * @param sdkIdentifyResultPushLog 相机识别结果
     * @param parkArmInfo              出入口配置信息
     * @param configParkInfo           车场临时车、月租车过期配置
     * @param parkCarMonthly           最近一条月租车信息
     * @return boolean
     * @author jdd孙庆伟
     * @date 2021/9/3 17:46:31
     * @version 1.0
     */
    private boolean parkCarMonthlyOverTime(SdkIdentifyResultPushLog sdkIdentifyResultPushLog, ParkArmInfo parkArmInfo, ConfigParkInfo configParkInfo, ParkCarMonthly parkCarMonthly) throws Exception {
        String serialNo = sdkIdentifyResultPushLog.getSerialNo();
        String plateNo = sdkIdentifyResultPushLog.getLicense();
        log.info("月租车:{}", plateNo + "过期处理");
        //月租车过期处理方式 1、月租车过期禁止入场
        if (configParkInfo.getHandleMonthlyRentalCar().equals(JddConstant.ConfigParkInfo.HANDLE_MONTHLY_RENTAL_CAR_ONE)) {
            log.info("月租车:{}", plateNo + "过期禁止入场");
            ledCallInterface.BanMonthOverAll(serialNo, plateNo, parkCarMonthly.getEndTime(), null);
            return true;
        }
        //月租车过期处理方式 2、月租车过期按照临时车收费
        if (configParkInfo.getHandleMonthlyRentalCar().equals(JddConstant.ConfigParkInfo.HANDLE_MONTHLY_RENTAL_CAR_TWO)) {
            log.info("月租车:{}", plateNo + "过期按临时车收费入场");
            cacheUtil.put("parkToEnd", plateNo, JddConstant.OverdueType.MONTH_OVERDUE);
//            ledCallInterface.EntranceMonthlyOverdueAll(serialNo, plateNo);
//            saveParkCarEnterLog(sdkIdentifyResultPushLog, parkArmInfo, JddConstant.MqttRecResultPushLog.IS_WHITE_ZERO);
            return false;
        }
        // 过期n天后禁止入场
        if (configParkInfo.getHandleMonthlyRentalCar().equals(JddConstant.ConfigParkInfo.HANDLE_MONTHLY_RENTAL_CAR_THREE)) {
            log.info("月租车:{}", plateNo + "过期n天后禁止入场");
            //过期时间
            Date endTimes = parkCarMonthly.getEndTime();
            //月租车过期几天后禁止入场
            Integer overDay = configParkInfo.getOverDay();
            Calendar calendar = new GregorianCalendar();
            calendar.setTime(endTimes);
            calendar.add(Calendar.DATE, overDay);
            long time = calendar.getTime().getTime();
            long date = System.currentTimeMillis();
            if (date > time) {
                log.info("月租车:{}", plateNo + "过期n天禁止入场");
                ledCallInterface.BanMonthOverDayAll(serialNo, plateNo, endTimes);
            } else {
                log.info("月租车:{}", plateNo + "过期n天按临时车收费入场");
//                ledCallInterface.EntranceMonthOverCanAll(serialNo, plateNo);
//                saveParkCarEnterLog(sdkIdentifyResultPushLog, parkArmInfo, JddConstant.MqttRecResultPushLog.IS_WHITE_ZERO);
                cacheUtil.put("parkToEnd", plateNo, JddConstant.OverdueType.MONTH_OVERDUE);
                return false;
            }
            return true;
        }
        return false;
    }

    /**
     * 车辆入场保存
     *
     * @param sdkIdentifyResultPushLog 车辆识别信息
     * @param parkArmInfo              停车场出入口配置信息
     * @param isWhite                  0，临时车，1，月租车，2，储值车，3，军警车"
     */
    private void saveParkCarEnterLog(SdkIdentifyResultPushLog sdkIdentifyResultPushLog, ParkArmInfo parkArmInfo, String isWhite) throws Exception {
        ParkCarEnterLog carEnterLog = new ParkCarEnterLog();
        //组装车辆入场记录
        packageCarEnterLog(sdkIdentifyResultPushLog, parkArmInfo, carEnterLog, DateUtil.newDate(), isWhite);
        if (StringUtils.isNotBlank(parkArmInfo.getSourceAreaId())) {
            List<ParkCarEnterLog> carEnterLogList = getParkCarEnterLogList(sdkIdentifyResultPushLog.getLicense());
            if (CollectionUtil.isNotEmpty(carEnterLogList)) {
                ParkCarEnterLog lastCarEnterLog = carEnterLogList.get(0);
                //以前有入场记录，而且又是月租车，则把原来的入场记录的月租车车位改成未停靠
                if (StringUtils.isNotBlank(lastCarEnterLog.getParkNumber())) {
                    parkSpaceService.updateParkStatus(lastCarEnterLog, JddConstant.ParkSpace.PARKSTATUS_ZERO);

                    //发送HTTP请求云端同步更新月租车位状态
                    RestTemplateSpaceVo restTemplateSpaceVo = new RestTemplateSpaceVo();
                    restTemplateSpaceVo.setLatestParkCarEnterLogStr(JSONObject.toJSONString(lastCarEnterLog));
                    log.info("发送请求到云端同步更新月租车车位停靠状态:{}", sendCloudSynSpace(restTemplateSpaceVo));
                }
                // 小车场入口-->入场记录中入场批次号存最近入场记录的车位号
                carEnterLog.setBatchNumber(lastCarEnterLog.getBatchNumber());
            }
        } else {
            // 大车场入口-->入场记录中入场批次号是雪花算法生产的值
            //有入大车场但未出场的入场记录，有则异常并记录
            parkCarOutAbnormalLogService.findAbnormals(parkArmInfo.getTargetAreaId(), sdkIdentifyResultPushLog.getLicense());
            parkCarEnterLogMapper.updateIsOut(sdkIdentifyResultPushLog.getLicense());
            carEnterLog.setBatchNumber(carEnterLog.getId());
        }
        parkCarEnterLogService.save(carEnterLog);
        //发送webSocket消息 并上传云端入场记录
        sendMessage(carEnterLog);
    }

    /**
     * 根据相机识别日志和车道信息组装车辆入场记录
     *
     * @param sdkIdentifyResultPushLog 相机识别日志
     * @param parkArmInfo              车道信息
     * @param carEnterTime             车辆入场时间
     * @return 车辆入场记录
     */

    private ParkCarEnterLog packageCarEnterLog(SdkIdentifyResultPushLog sdkIdentifyResultPushLog, ParkArmInfo parkArmInfo, ParkCarEnterLog carEnterLog, String carEnterTime, String isWhite) {
        carEnterLog.setId(SnowFlakeUtil.getId());
        carEnterLog.setParkCode(parkArmInfo.getParkCode());
        carEnterLog.setParkName(parkArmInfo.getParkName());
        carEnterLog.setAreaId(parkArmInfo.getTargetAreaId());
        carEnterLog.setArmName(parkArmInfo.getArmName());
        carEnterLog.setSerialNo(parkArmInfo.getSerialNo());
        carEnterLog.setIpAddr(parkArmInfo.getIpAddr());
        carEnterLog.setPlateNo(sdkIdentifyResultPushLog.getLicense());
        carEnterLog.setCarEnterTime(carEnterTime);
        carEnterLog.setImgPath(sdkIdentifyResultPushLog.getImg());
        if (null != sdkIdentifyResultPushLog.getOssImg()) {
            carEnterLog.setOssImgPath(sdkIdentifyResultPushLog.getOssImg());
        }
        carEnterLog.setIsOut("0");
        carEnterLog.setCarNature(isWhite);
        carEnterLog.setCarTypeNo(String.valueOf(sdkIdentifyResultPushLog.getNType()));
        carEnterLog.setCarType(null != sdkIdentifyResultPushLog.getLicense() ? MoblieControllerUtils.HAVEPLATE : MoblieControllerUtils.NONEPLATE);
        if (null != sdkIdentifyResultPushLog.getLicense() && ("_无_".equals(sdkIdentifyResultPushLog.getLicense()))) {
            carEnterLog.setCarType(MoblieControllerUtils.NONEPLATE);
        }
        carEnterLog.setCreateTime(new Date());
        //加上工作站名和岗亭人账户
        String workUser = parkWorkStationService.getStationOperationNameByParkArm(parkArmInfo);
        if (ObjectUtil.isNotEmpty(workUser)) {
            carEnterLog.setWorkName(parkArmInfo.getWorkStationName());
            carEnterLog.setSysUserName(workUser);
        }
        //openid不为空-无牌车
        if (StringUtils.isNotBlank(sdkIdentifyResultPushLog.getOpenid())) {
            carEnterLog.setOpenid(sdkIdentifyResultPushLog.getOpenid());
        }
        return carEnterLog;
    }

    /**
     * 月租车保存云端入场记录，更新车位表状态 并上传云端
     *
     * @param parkCarEnterLog
     * @param latestParkCarEnterLog
     * @return void
     * @author jdd孙庆伟
     * @date 2021/9/3 16:15:59
     * @version 1.0
     */
    public void addParkCarMonthlyEnterLog(ParkCarEnterLog parkCarEnterLog, ParkCarEnterLog latestParkCarEnterLog) throws IOException {
        //保存入场记录
        parkCarEnterLogService.save(parkCarEnterLog);

        // 将获得的车位的停车状态改为有车辆停靠
        parkSpaceService.updateParkStatus(parkCarEnterLog, JddConstant.ParkSpace.PARKSTATUS_ONE);

        // 将之前获得的车位的停车状态改为无车辆停靠
        parkSpaceService.updateParkStatus(latestParkCarEnterLog, JddConstant.ParkSpace.PARKSTATUS_ZERO);

        //发送webSocket消息 并上传云端入场记录
        sendMessage(parkCarEnterLog);

        //发送HHTP请求云端同步月租车位状态
        RestTemplateSpaceVo restTemplateSpaceVo = new RestTemplateSpaceVo();
        restTemplateSpaceVo.setParkCarEnterLogStr(JSONObject.toJSONString(parkCarEnterLog));
        restTemplateSpaceVo.setLatestParkCarEnterLogStr(JSONObject.toJSONString(latestParkCarEnterLog));
        log.info("发送请求云端同步月租车位状态:{}", sendCloudSynSpace(restTemplateSpaceVo));
    }

    /**
     * 发送请求云端同步月租车位状态
     *
     * @param restTemplateSpaceVo
     * @return com.jdd.common.api.vo.ResultVo
     * @author jdd孙庆伟
     * @date 2021/10/15 14:15:05
     * @version 1.0
     */
    public ResultVo sendCloudSynSpace(RestTemplateSpaceVo restTemplateSpaceVo) {
        String url = "";
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        //封装参数
        MultiValueMap<String, String> stringStringSortedMap = EncapsulationSignDataUtils.encapsulationParameters(restTemplateSpaceVo, appId, appScrect);
        // 组装请求体
        HttpEntity<MultiValueMap<String, String>> request =
                new HttpEntity<MultiValueMap<String, String>>(stringStringSortedMap, headers);
        ResponseEntity<ResultVo> cmsResultDTO = restTemplateUtils.post(url, request, ResultVo.class);
        return cmsResultDTO.getBody();
    }

    /**
     * 发送webSocket消息 并上传云端入场记录
     *
     * @param parkCarEnterLog
     * @return void
     * @author jdd孙庆伟
     * @date 2021/9/3 17:37:02
     * @version 1.0
     */
    public void sendMessage(ParkCarEnterLog parkCarEnterLog) throws IOException {
        //webSocket发送监控中心数据信息
        ParkArmInfo parkArmInfo = parkArmInfoService.findParkArmInfoBySerialNo(parkCarEnterLog.getSerialNo());
        String workUser = parkWorkStationService.getStationOperationNameByParkArm(parkArmInfo);
        Map<String, Object> data = parkArmInfoService.getData(workUser);

        socketServer.sendMessageAll(JSONObject.toJSONString(data));
        //调用云端保存入场记录接口 如果失败重试三次 并返回云端保存结果
        String url = "";
        //上传云端 图片路径修改成oss图片路径
        parkCarEnterLog.setImgPath(parkCarEnterLog.getOssImgPath());
        //云端保存结果 成功200 失败 400
        String remoteResults = getRemoteResults(url, parkCarEnterLog);
        //如果云端保存出场记录成功则修改场端出场记录 是否同步成功状态
        if (remoteResults.equals(JddConstant.SaveCloudType.SAVE_SUCCESS)) {
            parkCarEnterLogMapper.editSynchronousStatus(parkCarEnterLog.getId(), remoteResults);
        }
    }

    /**
     * 入场记录
     *
     * @param plateNo 车牌号
     * @return
     */
    private List<ParkCarEnterLog> getParkCarEnterLogList(String plateNo) {
        QueryWrapper<ParkCarEnterLog> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("plate_no", plateNo);
        queryWrapper.eq("is_out", JddConstant.StringNumber.STRING_ZERO);
        queryWrapper.orderByDesc("car_enter_time");
        return parkCarEnterLogService.list(queryWrapper);
    }

    /**
     * 查询月租车空余车位
     *
     * @param areaId             入场车道目标区域id
     * @param parkCarMonthlyList 车辆在区域内未过期的月租车信息
     * @return java.lang.String     车位号
     * @version 1.0
     * @author renshangyi
     * @date 2021-08-17
     */
    private String getMonthLeisureParkNumber(String areaId, List<ParkCarMonthly> parkCarMonthlyList) {
        if (CollectionUtil.isEmpty(parkCarMonthlyList)) {
            return null;
        }
        // 得到月租车在区域内所有未过期的月租车位号
        List<String> parkNumbersInCarMonthlyList = parkCarMonthlyList.stream().map(ParkCarMonthly::getParkNumber).distinct().collect(Collectors.toList());
        // 查询月租车在区域内所有未过期的月租车位中未被使用的月租车信息，并根据过期时间倒序排序
        LambdaQueryWrapper<ParkSpace> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ParkSpace::getAreaId, areaId);
        queryWrapper.eq(ParkSpace::getParkStatus, JddConstant.ParkSpace.PARKSTATUS_ZERO);
        queryWrapper.in(true, ParkSpace::getParkNumber, parkNumbersInCarMonthlyList);
        queryWrapper.orderByDesc(ParkSpace::getEndTime);

        List<ParkSpace> parkSpaces = parkSpaceService.list(queryWrapper);
        if (CollectionUtils.isNotEmpty(parkSpaces)) {
            return parkSpaces.get(0).getParkNumber();
        }
        return null;
    }

    /**
     * 获得云端出/入场记录是否保存成功
     *
     * @param url    远程访问接口
     * @param object 远程传递参数
     * @return java.lang.String
     * @author jdd孙庆伟
     * @date 2021/5/28 15:25:57
     * @version 1.0
     */
    public String getRemoteResults(String url, Object object) {
        //调用云端入场记录保存
        int restTemplateCode = (int) GuavaRetryingUtils.retrySend(object, url, restTemplateUtils, appId, appScrect);
        //restTemplateCode为200则云端保存成功 restTemplateCode为400则云端保存失败
        return ResultVo.SUCCESS_CODE == restTemplateCode ? JddConstant.SaveCloudType.SAVE_SUCCESS : JddConstant.SaveCloudType.SAVE_ERROR;
    }

    /**
     * 车场入场确认开闸 发送webSockt消息 车场监控确认开闸
     *
     * @param serialNo
     * @param plateNo
     * @param parkArmInfo
     * @param vehicleType
     * @return void
     * @author jdd孙庆伟
     * @date 2021/8/28 15:40:15
     * @version 1.0
     */
    public void sendInfo(String serialNo, String plateNo, ParkArmInfo parkArmInfo, String vehicleType, String parkingIsFull, String imgPath) {
        try {
            WebSocketVo webSocketVo = new WebSocketVo();
            webSocketVo.setPlateNo(plateNo);
            webSocketVo.setVehicleType(vehicleType);
            webSocketVo.setArmName(parkArmInfo.getArmName());
            webSocketVo.setArmType(parkArmInfo.getArmType());
            webSocketVo.setSerialNo(serialNo);
            webSocketVo.setArmId(parkArmInfo.getId());
            webSocketVo.setParkingIsFull(parkingIsFull);
            webSocketVo.setImgPath(imgPath);
            webSocketVo.setCarEnterTime(DateUtil.fomatDateTimeStr(new Date()));
            socketServer.sendInfo(JSONObject.toJSONString(webSocketVo), parkArmInfo.getUsedUserId());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 欢迎光临播报显示
     *
     * @param identifyResultPushLog 识别结果
     * @param parkArmInfo           车道信息
     * @param serialNo              序列号
     * @param plateNo               车牌号
     * @param carNature             车辆类型
     * @return boolean
     * @author jdd孙庆伟
     * @date 2021/9/3 11:28:08
     * @version 1.0
     */
    private boolean welcomeDeal(SdkIdentifyResultPushLog identifyResultPushLog, ParkArmInfo parkArmInfo, String serialNo, String plateNo, String carNature) throws Exception {
        ledCallInterface.EntranceSpecialAll(serialNo, plateNo);
        saveParkCarEnterLog(identifyResultPushLog, parkArmInfo, carNature);
        log.info("白名单车辆，未过期通行");
        return true;
    }

    /**
     * 通过设备序列号、停车场编号、车辆类型配置id 获取停车场通行配置信息
     *
     * @param serialNo    设备序列号
     * @param carSmTypeId 车辆类型配置id
     * @return java.lang.Boolean
     * @author jdd孙庆伟
     * @date 2021/5/26 18:31:23
     * @version 1.0
     */
    public Boolean ifPassage(String serialNo, String carSmTypeId, String plateNo) throws Exception {
        ConfigParkThrough configParkThrough = parkThroughService.queryConfigParkThrough(serialNo);
        //如果存在通行配置则进入通行处理
        if (null != configParkThrough) {
            //获取车场通行配置信息 车辆类型id
            String carTypeId = configParkThrough.getCarTypeId();
            //判断当前车辆类型是否在车场通行配置
            boolean str = strContains(carTypeId, carSmTypeId);
            if (str) {
                //获取车辆类型禁行日
                return !judgeStopDay(configParkThrough, serialNo, plateNo);
            }
        }
        return true;
    }

    /**
     * 匹配车辆类型
     *
     * @param str       车辆类型配置id(多个按逗号分开)
     * @param carTypeId 当前车辆类型id
     * @return boolean
     * @author jdd孙庆伟
     * @date 2021/9/6 10:40:00
     * @version 1.0
     */
    private boolean strContains(String str, String carTypeId) {
        String[] split = str.split(",");
        List<String> list = new ArrayList<>();
        Collections.addAll(list, split);
        return list.contains(carTypeId);
    }

    /**
     * @param configParkThrough
     * @param serialNo
     * @return
     * @Description 判断车辆是否在禁行日或者禁行时段内
     * @author shangyue
     * @Date 2021/3/31
     */
    private boolean judgeStopDay(ConfigParkThrough configParkThrough, String serialNo, String plateNo) throws Exception {
        String stopWeeklyDay = configParkThrough.getStopWeeklyDay();
        if (!getWeek(stopWeeklyDay)) {
            long startTime = DateUtil.newLong(configParkThrough.getStopStartTime());
            long endTime = DateUtil.newLong(configParkThrough.getStopEndTime());
            long newTime = DateUtil.newLong(DateUtil.newTime());
            if (newTime >= startTime && newTime <= endTime) {
                ledCallInterface.JudgeStopDayAll(serialNo, plateNo);
                return true;
            }
        }
        return false;
    }

    private boolean getWeek(String stopWeeklyDay) {
        if (null == stopWeeklyDay) {
            return true;
        }
        String[] split = stopWeeklyDay.split(",");
        List<String> str = new ArrayList<>();
        Collections.addAll(str, split);
        String week = null;
        Date today = new Date();
        Calendar c = Calendar.getInstance();
        c.setTime(today);
        int weekday = c.get(Calendar.DAY_OF_WEEK);
        if (weekday == 1) {
            week = "7";
        } else if (weekday == 2) {
            week = "1";
        } else if (weekday == 3) {
            week = "2";
        } else if (weekday == 4) {
            week = "3";
        } else if (weekday == 5) {
            week = "4";
        } else if (weekday == 6) {
            week = "5";
        } else if (weekday == 7) {
            week = "6";
        }
        return !str.contains(week);
    }

    /**
     * 车辆类型比对
     *
     * @param type 车辆类型
     * @return java.lang.Boolean
     * @author jdd孙庆伟
     * @date 2021/9/3 11:29:19
     * @version 1.0
     */
    private Boolean freeIf(String type) {
        List<String> list = CarType.getFreeEnum();
        return list.contains(type);
    }

    /**
     * 根据车辆编号、车牌类型匹配停车场的车辆类型Id
     *
     * @param type 车牌类型
     * @return java.lang.String
     * @version 1.0
     */
    private String getCarType(int type) {
        String result = null;
        List<ConfigCarType> list = carTypeService.getCarTypeList();
        for (ConfigCarType carType : list) {
            String cardTypeId = carType.getId();
            List<String> typeNo = Arrays.asList(carType.getCarTypeNo().split(","));
            if (typeNo.contains(type + "")) {
                result = cardTypeId;
                return result;
            }
        }
        return result;
    }

    /**
     * 保存停车订单
     *
     * @param parkArmInfo
     * @param identifyResultPushLog
     * @param carTypeIds
     * @param plateNo
     * @param fee
     * @return java.util.Map<java.lang.String, java.lang.Object>
     * @author jdd孙庆伟
     * @date 2021/5/8 10:01:33
     * @version 1.0
     */
    public Map<String, Object> generateParkOrderInfoVO(ParkArmInfo parkArmInfo, SdkIdentifyResultPushLog identifyResultPushLog, String carTypeIds, String plateNo, long fee) {
        Map<String, Object> data = new HashMap<>(8);
        ParkOrderInfoVO parkOrderInfoVO = new ParkOrderInfoVO();
        String carType = getCarType(Integer.parseInt(carTypeIds));
        parkOrderInfoVO.
                setId(SnowFlakeUtil.getId()).
                setIpAddr(parkArmInfo.getIpAddr()).
                setSerialNo(parkArmInfo.getSerialNo()).
                setParkCode(parkArmInfo.getParkCode()).
                setParkName(parkArmInfo.getParkName()).
                setOutImgPath(identifyResultPushLog.getOssImg()).
                setCarTypeId(carType).
                setType(identifyResultPushLog.getNType().toString()).
                setPlateNo(plateNo).
                setChargeUnpaid(fee + "").
                setArmName(parkArmInfo.getArmName()).
                setChargeTotalFee(fee + "").
                setOrderStatus("1");
        data.put("parkOrderInfo", parkOrderInfoVO);
        return data;
    }

    /**
     * 判断当前时间是否在[startTime, endTime]区间，注意时间格式要一致
     *
     * @param nowTime   当前时间
     * @param startTime 开始时间
     * @param endTime   结束时间
     * @return
     * @author jqlin
     */
    public static boolean isEffectiveDate(Date nowTime, Date startTime, Date endTime) {
        if (nowTime.getTime() == startTime.getTime()
                || nowTime.getTime() == endTime.getTime()) {
            return true;
        }

        Calendar date = Calendar.getInstance();
        date.setTime(nowTime);

        Calendar begin = Calendar.getInstance();
        begin.setTime(startTime);

        Calendar end = Calendar.getInstance();
        end.setTime(endTime);

        return date.after(begin) && date.before(end);
    }
}
